package top.techqi.view.recyclerview.tile

import android.util.SparseArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView

private typealias NakedTilePortion = TilePortion<TileHolder<TileRecord>, TileRecord>

@Suppress("UNCHECKED_CAST")
class TileAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private lateinit var layoutInflater: LayoutInflater

    private val holderClassMap = HashMap<Class<*>, NakedTilePortion>()
    private val recordClassMap = HashMap<Class<*>, NakedTilePortion>()
    private val viewTypeSparse = SparseArray<NakedTilePortion>()

    private val records = ArrayList<TileRecord>()

    inline fun <reified VH : TileHolder<R>, reified R : TileRecord>
            register(portion: TilePortion<VH, R>) {
        register(VH::class.java, R::class.java, portion)
    }

    fun <VH : TileHolder<R>, R : TileRecord> register(
        holderClass: Class<VH>, recordClass: Class<R>, portion: TilePortion<VH, R>
    ) {
        portion as NakedTilePortion
        holderClassMap[holderClass] = portion
        recordClassMap[recordClass] = portion
        viewTypeSparse[portion.viewType] = portion
    }

    fun commit(data: List<TileRecord>) {
        val dc = DiffCallback(this.records, data)
        val dr = DiffUtil.calculateDiff(dc)

        records.clear()
        records.addAll(data)
        dr.dispatchUpdatesTo(this)
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        layoutInflater = LayoutInflater.from(recyclerView.context)
    }

    override fun getItemCount(): Int {
        return records.size
    }

    override fun getItemViewType(position: Int): Int {
        return recordClassMap[records[position].javaClass]!!.viewType
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return viewTypeSparse[viewType]?.onMakeHolder(layoutInflater, parent)!!
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val portion = holderClassMap[holder::class.java]!!
        val record = records[position]
        holder as TileHolder<TileRecord>
        holder.record = record
        portion.onBindRecord(holder, record)
    }

    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>
    ) {
        val portion = holderClassMap[holder::class.java]!!
        val record = records[position]
        holder as TileHolder<TileRecord>
        holder.record = record
        portion.onBindRecord(holder, record, payloads)
    }

    override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
        val portion = holderClassMap[holder::class.java]!!
        holder as TileHolder<TileRecord>
        portion.onAttached(holder)
    }

    override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
        val portion = holderClassMap[holder::class.java]!!
        holder as TileHolder<TileRecord>
        portion.onDetached(holder)
    }

    private inner class DiffCallback(
        private val oldList: List<TileRecord>,
        private val newList: List<TileRecord>
    ) : DiffUtil.Callback() {
        override fun getOldListSize(): Int {
            return oldList.size
        }

        override fun getNewListSize(): Int {
            return newList.size
        }

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            val old = oldList.getOrNull(oldItemPosition) ?: return false
            val new = newList.getOrNull(newItemPosition) ?: return false
            return old.javaClass == new.javaClass && old.index == new.index
        }

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
            val old = oldList.getOrNull(oldItemPosition) ?: return false
            val new = newList.getOrNull(newItemPosition) ?: return false
            return old == new
        }

        override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
            val old = oldList.getOrNull(oldItemPosition) ?: return null
            val new = newList.getOrNull(newItemPosition) ?: return null
            return recordClassMap[old.javaClass]?.diffPayload(old, new)
        }
    }
}
